package jEDF.EDF;

import cs.jwave.JWave;
import cs.jwave.Transform;
import cs.jwave.handlers.BasicTransform;
import cs.jwave.handlers.FastWaveletTransform;
import cs.jwave.handlers.wavelets.Coif06;
import java.io.*;

import javax.swing.*;

import jEDF.FFT.*;
import jEDF.JEDF.*;

/**
 *
 * <p>Titre : jEDF</p>
 * <p>Description : Java European Data Format Viewer and Analyser</p>
 * <p>Author : Nizar Kerkeni</p>
 * <p>Copyright : Copyright (c) 2003-2006</p>
 * <p>Version 2.0</p>
 */
public class EDFSignal {

    private String label = null;
    private String transducerType = null;
    private String physDim = null;
    private double physMin = 0.0;
    private double physMax = 0.0;
    private long digMin = 0;
    private long digMax = 0;
    private String prefiltering = null;
    private long nbSamples = 0;
    private double samplingRate = 0;
    private int numSignal = -1;
    private static final int maxHz = 33;
    private EDFSignalData signalData = null;
    private EDFFile edfFile = null;
    private String sep = "\t"; //seperator \t by lines \n by column

    /**
     * @param edfFile
     *            the EDF file to extract the signal from
     * @param numSignal
     *            the signal number
     * @throws IOException
     *             if the EDF file cannot be accessed
     */
    public EDFSignal(EDFFile edfFile, int numSignal)
            throws IOException {
        this.numSignal = numSignal;
        this.edfFile = edfFile;

        signalData = new EDFSignalData(this, edfFile);

        EDFSignalHeader signalHeader = new EDFSignalHeader(edfFile.getRandomAccessFile(), numSignal, edfFile.getNbSignals());

        label = signalHeader.getLabel();
        transducerType = signalHeader.getTransducerType();
        physDim = signalHeader.getPhysicalDimension();
        physMin = signalHeader.getPhysicalMin();
        physMax = signalHeader.getPhysicalMax();
        digMin = signalHeader.getDigitalMin();
        digMax = signalHeader.getDigitalMax();
        prefiltering = signalHeader.getPrefiltering();
        nbSamples = signalHeader.getNbSamples();
    }

    /**
     * @param signal
     *            the signal to create a copy from
     */
    public EDFSignal(EDFSignal signal) {
        label = signal.label;
        transducerType = signal.transducerType;
        physDim = signal.physDim;
        physMin = signal.physMin;
        physMax = signal.physMax;
        digMin = signal.digMin;
        digMax = signal.digMax;
        prefiltering = signal.prefiltering;
        nbSamples = signal.nbSamples;
        numSignal = signal.numSignal;
        signalData = signal.signalData;
        edfFile = signal.edfFile;
    }

    /**
     * @return Returns the digital Max.
     */
    public int getDigMax() {
        return (int) digMax;
    }

    /**
     * @return Returns the digital Min.
     */
    public int getDigMin() {
        return (int) digMin;
    }

    /**
     * @return Returns the label.
     */
    public String getLabel() {
        return label;
    }

    /**
     * @return Returns the number of samples per record.
     */
    public int getNbSamples() {
        return (int) nbSamples;
    }

    /**
     * @return Returns the signal number.
     */
    public int getNumSignal() {
        return numSignal;
    }

    /**
     * @return Returns the physical Dimension.
     */
    public String getPhysDim() {
        return physDim;
    }

    /**
     * @return Returns the physical Max.
     */
    public double getPhysMax() {
        return physMax;
    }

    /**
     * @return Returns the physical Min.
     */
    public double getPhysMin() {
        return physMin;
    }

    /**
     * @return Returns the prefiltering.
     */
    public String getPrefiltering() {
        return prefiltering;
    }

    /**
     * @return Returns the signalData.
     */
    public EDFSignalData getSignalData() {
        return signalData;
    }

    /**
     * @return Returns the transducer Type.
     */
    public String getTransducerType() {
        return transducerType;
    }

    /**
     * @param startSecond
     *            the second to start reading from
     * @param nbSeconds
     *            the number of seconds to read
     * @return the read data
     */
    public short[] readSeconds(int startSecond, int nbSeconds) {
        return signalData.readSeconds(startSecond, nbSeconds);
    }

    /**
     * this fonction return the data corresponding to the given number of
     * seconds plus a point, useful if the data are to be displayed like a graph
     *
     * @param startSecond
     *            the second to start reading from
     * @param nbSeconds
     *            the number of seconds to read
     * @return the read data
     */
    public short[] readSecondsAndAPoint(int startSecond, int nbSeconds) {
        return signalData.readSecondsAndAPoint(startSecond, nbSeconds);
    }

    /**
     * @return true if this signal appears to contain hypnogram data
     */
    public boolean isEmbeddedHypnogram() {
        boolean result = true;

        result = result && getPhysMin() == 0.0;
        result = result && getPhysMax() == 9.0;
        result = result && getDigMin() == 0;
        result = result && getDigMax() == 9;
        result = result && getNbSamples() == 1;

        return result;
    }

    private class SaveToTextThread extends Thread {

        private File file = null;
        private int startSecond = 0;
        private int endSecond = 0;
        private int secondsPerLine = 0;
        private int coef = 1;
        private JProgressBar jProgressBar = null;
        private EDFSignalToTextDialog signalToTextDialog;

        /**
         * @param file
         *            the file to save to
         * @param startSecond
         *            the second to start saving
         * @param endSecond
         *            the second to end saving
         * @param secondsPerLine
         *            the number of seconds per line, 0 if just a number per
         *            line
         * @param jProgressBar
         *            the progress bar to update during the saving process
         * @param signalToTextDialog
         *            the dialog box to close when the saving process if
         *            finished
         * @throws IllegalArgumentException
         *             if the given parameters are erronous
         */
        public SaveToTextThread(File file, int startSecond, int endSecond,
                int secondsPerLine, JProgressBar jProgressBar,
                EDFSignalToTextDialog signalToTextDialog)
                throws IllegalArgumentException {
            if (startSecond < 0) {
                throw new IllegalArgumentException();
            }
            if (endSecond <= 0) {
                throw new IllegalArgumentException();
            }
            if (secondsPerLine > (endSecond - startSecond)) {
                throw new IllegalArgumentException();
            }
            if ((endSecond - startSecond) <= 0) {
                throw new IllegalArgumentException();
            }
            if (file == null) {
                throw new IllegalArgumentException();
            }

            this.file = file;
            this.startSecond = startSecond;
            this.endSecond = endSecond;
            this.secondsPerLine = secondsPerLine;
            this.jProgressBar = jProgressBar;
            this.signalToTextDialog = signalToTextDialog;
            if (signalToTextDialog.getsignalInverted() == true) {
                System.out.println("It's inverted");
                coef = -1;
            }
            start();
        }

        /*
         * (non-Javadoc)
         *
         * @see java.lang.Thread#run()
         */
        public void run() {
            try {
                RandomAccessFile raf = new RandomAccessFile(file, "rw");

                short[] data = null;
                int selectedSampling = signalToTextDialog.getSelectedSamplingRate();
                double samplesPerSecond = getSamplingRate();
                int step = (int) (samplesPerSecond / selectedSampling);

                double nbSecondsToWrite = endSecond - startSecond;

                int nbSamplesToWrite = (int) Math.round(nbSecondsToWrite * selectedSampling);

                jProgressBar.setMinimum(0);
                jProgressBar.setMaximum(nbSamplesToWrite - 1);
                jProgressBar.setStringPainted(true);
                signalToTextDialog.pack();

                StringBuffer line = new StringBuffer("Channel : " + label);
                raf.writeBytes(line.toString() + "\n");

                line = new StringBuffer("Epochs  : " + edfFile.getNbRecords());
                raf.writeBytes(line.toString() + "\n");

                line = new StringBuffer("Samples : " + nbSamples + " / " +
                        edfFile.getRecordDuration() + " s");
                raf.writeBytes(line.toString() + "\n");

                if (secondsPerLine == 0) { // Convert by epoch !!
                    int startSecond = this.startSecond;
                    for (int i = 0; i < nbSamplesToWrite && signalToTextDialog.isVisible();) {
                        data = readSeconds(startSecond, edfFile.getRecordDuration());
                        startSecond += edfFile.getRecordDuration();
                        for (int j = 0; j < data.length && i < nbSamplesToWrite; j += step) {
                            jProgressBar.setValue(i);
                            raf.writeBytes((data[j] * coef) + "\n"); //****************************************************
                            i++;
                        }
                    }
                } else { // Convert by seconds
                    int startSecond = this.startSecond;
                    line.setLength(0);
                    for (int i = 0; i < nbSamplesToWrite && signalToTextDialog.isVisible();) {

                        data = readSeconds(startSecond, secondsPerLine);
                        startSecond += secondsPerLine;
                        for (int j = 0; j < data.length && i < nbSamplesToWrite; j += step) {
                            line.append((data[j] * coef) + sep); //*********************************************************
                            i++;
                        }
                        jProgressBar.setValue(i);
                        raf.writeBytes(line.toString() + "\n");
                        line.setLength(0);
                    }
                }
                raf.close();

                System.gc();

                if (!signalToTextDialog.isVisible()) {
                    JOptionPane.showMessageDialog(null, JEDFMainWindow.getLocalizedString(
                            "The operation has been aborted on user request."),
                            JEDFMainWindow.getLocalizedString("Aborted"),
                            JOptionPane.WARNING_MESSAGE);
                    signalToTextDialog.dispose();
                } else {
                    JOptionPane.showMessageDialog(null, JEDFMainWindow.getLocalizedString(
                            "The operation has been completed successfully !"),
                            JEDFMainWindow.getLocalizedString("Success"),
                            JOptionPane.INFORMATION_MESSAGE);
                    signalToTextDialog.dispose();
                }
            } catch (IOException e) {
                JOptionPane.showMessageDialog(null, e.getLocalizedMessage(),
                        JEDFMainWindow.getLocalizedString("Error"),
                        JOptionPane.ERROR_MESSAGE);
                signalToTextDialog.dispose();
            }
        }
    }

    /**
     * @param file
     *            the file to save to
     * @param startEpoch
     *            the epoch to start saving from
     * @param endEpoch
     *            the end epoch
     * @param secondsPerLine
     *            the number of seconds per line
     * @param jProgressBar
     *            the progress bar to update during the saving process
     * @param signalToTextDialog
     *            the dialog box to close at the end of the operation
     * @throws IllegalArgumentException
     *             if parameters are erronous
     */
    public void saveToTextByEpoch(File file, int startEpoch, int endEpoch,
            int secondsPerLine, JProgressBar jProgressBar,
            EDFSignalToTextDialog signalToTextDialog)
            throws
            IllegalArgumentException {
        startEpoch *= edfFile.getRecordDuration();
        endEpoch *= edfFile.getRecordDuration();

        new SaveToTextThread(file, startEpoch, endEpoch, secondsPerLine,
                jProgressBar, signalToTextDialog);
    }

    /**
     * @param file
     *            the file to save to
     * @param startEpoch
     *            the epoch to start saving from
     * @param endEpoch
     *            the end epoch
     * @param jProgressBar
     *            the progress bar to update during the saving process
     * @param signalToTextDialog
     *            the dialog box to close at the end of the operation
     * @throws IllegalArgumentException
     *             if parameters are erronous
     */
    public void saveToTextByEpoch(File file, int startEpoch, int endEpoch,
            JProgressBar jProgressBar,
            EDFSignalToTextDialog signalToTextDialog)
            throws
            IllegalArgumentException {
        startEpoch *= edfFile.getRecordDuration();
        endEpoch *= edfFile.getRecordDuration();

        new SaveToTextThread(file, startEpoch, endEpoch, 0, jProgressBar,
                signalToTextDialog);
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Object#toString()
     */
    public String toString() {
        return getLabel();
    }

    private double[] getFourierTransformedData(int startSecond, int nbSeconds) {
        short[] data = readSeconds(startSecond, nbSeconds);
        return FastFourierTransform.processDataRealFFT(data);
    }

    public void fftToText1Hz(FFTExportDialog fftExportDialog, File file,
            int startEpoch, int endEpoch, int duration) {
        new FFTAnalysisThread(fftExportDialog, file, startEpoch, endEpoch,
                duration, true);
    }

    public void fftToTextBySleepBands(FFTExportDialog fftExportDialog,
            File file, int startEpoch, int endEpoch,
            int duration) {
        new FFTAnalysisThread(fftExportDialog, file, startEpoch, endEpoch,
                duration, false);
    }

    private class FFTAnalysisThread extends Thread {

        private FFTExportDialog fftExportDialog = null;
        private File file = null;
        private int startEpoch = 0;
        private int endEpoch = 0;
        private int duration = 0;
        private boolean raw = false;

        public FFTAnalysisThread(FFTExportDialog fftExportDialog, File file,
                int startEpoch, int endEpoch, int duration,
                boolean raw) {
            this.fftExportDialog = fftExportDialog;
            this.file = file;
            this.startEpoch = startEpoch;
            this.endEpoch = endEpoch;
            this.duration = duration;
            this.raw = raw;

            start();
        }

        public void run() {
            try {
                RandomAccessFile raf = new RandomAccessFile(file, "rw");

                StringBuffer sb = new StringBuffer();

                int startSecond = startEpoch * edfFile.getRecordDuration();
                int endSecond = endEpoch * edfFile.getRecordDuration();

                JProgressBar jProgressBar = fftExportDialog.getJProgressBar();
                jProgressBar.setMinimum(startSecond);
                jProgressBar.setMaximum(endSecond);

                double[] frequencyAxis = buildFrequencyAxis(
                        getFourierTransformedData(0, duration));

                if (raw) {
                    for (int i = 0; i < maxHz; i++) { //Header
                        sb.append(i + "Hz\t");
                    }
                    sb.deleteCharAt(sb.length() - 1);
                    sb.append('\n');
                    raf.writeBytes(sb.toString());
                    sb.setLength(0);
                    for (int i = startSecond; i < endSecond && fftExportDialog.isVisible(); i += duration) {
                        jProgressBar.setValue(i);

                        double data[] = getFourierTransformedData(i, duration);
                        data = postProcess1Hz(data, frequencyAxis);
                        for (int j = 0; j < data.length && fftExportDialog.isVisible(); j++) {
                            sb.append(data[j] + sep);
                        }
                        sb.deleteCharAt(sb.length() - 1);
                        sb.append('\n');
                        raf.writeBytes(sb.toString());
                        sb.setLength(0);
                    }
                } else {
                    raf.writeBytes("Delta\tTheta\tAlpha\tSigma\tBeta\n"); //Header
                    for (int i = startSecond; i < endSecond && fftExportDialog.isVisible(); i += duration) {
                        jProgressBar.setValue(i);

                        double data[] = getFourierTransformedData(i, duration);
                        data = postProcessBySleepBands(data, frequencyAxis);
                        for (int j = 0; j < data.length && fftExportDialog.isVisible(); j++) {
                            sb.append(data[j] + sep);
                        }
                        sb.deleteCharAt(sb.length() - 1);
                        sb.append('\n');
                        raf.writeBytes(sb.toString());
                        sb.setLength(0);
                    }
                }
                raf.close();

                System.gc();

                if (!fftExportDialog.isVisible()) {
                    JOptionPane.showMessageDialog(
                            null,
                            JEDFMainWindow.getLocalizedString(
                            "The operation has been aborted on user request."),
                            JEDFMainWindow.getLocalizedString("Aborted"),
                            JOptionPane.WARNING_MESSAGE);
                    fftExportDialog.dispose();
                } else {
                    JOptionPane.showMessageDialog(
                            null,
                            JEDFMainWindow.getLocalizedString(
                            "The operation has been completed successfully !"),
                            JEDFMainWindow.getLocalizedString("Success"),
                            JOptionPane.INFORMATION_MESSAGE);
                    fftExportDialog.dispose();
                }
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                JOptionPane.showMessageDialog(null, e.getLocalizedMessage());
            }
        }
    }

    private double[] buildFrequencyAxis(double[] fftData) {
        int dataLength = fftData.length;
        double samplingRate = getSamplingRate();
        double nbRawSamples = (double) dataLength * 2.0;
        double[] frequencyAxis = new double[dataLength];

        for (int i = 0; i < dataLength; i++) {
            frequencyAxis[i] = (double) i * samplingRate / nbRawSamples;
        }

        return frequencyAxis;
    }

    private double[] postProcess1Hz(double[] data, double[] frequencyAxis) {
        double[] result = new double[maxHz];

        int j;

        for (j = 0; frequencyAxis[j] <= 0.5; j++) {
            ; // Elimination of the frequencies < 0.5
        }
        for (int i = 0; i < result.length; i++) {
            for (; (j < frequencyAxis.length) && ((int) frequencyAxis[j]) < (i + 1); j++) {
                result[i] += data[j];
            }
        }
        return result;
    }

    private double[] postProcessBySleepBands(double[] data, double[] frequencyAxis) {
        // delta theta alpha sigma beta
        int[] sleepBands = {4, 8, 12, 16, 33};

        double[] result = new double[sleepBands.length];

        double[] tempData = postProcess1Hz(data, frequencyAxis);

        int j = 0;

        for (int i = 0; i < sleepBands.length; i++) {

            for (; j < (sleepBands[i]); j++) {
                result[i] += tempData[j];
            }
        }
        return result;
    }

    public double[] getFourierTransformedDataBy1Hz(int startSecond,
            int nbSeconds, double startPosition, double endPosition) {
        short[] selectedData = getSelectedSignalData(startSecond, nbSeconds,
                startPosition, endPosition);

        if (selectedData.length == 0) {
            return new double[0];
        }

        double[] fftData = FastFourierTransform.processDataRealFFT(selectedData);

        return postProcess1Hz(fftData, buildFrequencyAxis(fftData));
    }

    public short[] getSelectedSignalData(int startSecond, int nbSeconds,
            double startPosition, double endPosition) {
        short[] data = readSeconds(startSecond, nbSeconds);

        startPosition *= data.length;
        endPosition *= data.length;
        int selectedDataLength = (int) endPosition - (int) startPosition;

        short[] selectedData = new short[selectedDataLength];

        for (int i = 0; i < selectedDataLength; i++) {
            selectedData[i] = data[i + (int) startPosition];
        }

        return selectedData;
    }

    public double[] getFourierTransformedDataBySleepBands(int startSecond,
            int nbSeconds, double startPosition, double endPosition) {
        short[] selectedData = getSelectedSignalData(startSecond, nbSeconds,
                startPosition, endPosition);

        if (selectedData.length == 0) {
            return new double[0];
        }

        double[] fftData = FastFourierTransform.processDataRealFFT(selectedData);

        return postProcessBySleepBands(fftData, buildFrequencyAxis(fftData));
    }

    public double[] getWaveletTransformDataBy(int startSecond,
            int nbSeconds, double startPosition, double endPosition,
            BasicTransform trans) {
        short[] selectedData = getSelectedSignalData(startSecond, nbSeconds,
                startPosition, endPosition);

        if (selectedData.length == 0) {
            return new double[0];
        }


        double[] arrTime = new double[selectedData.length];
//        Transform t = new Transform(new FastWaveletTransform(new Coif06()));
        Transform t = new Transform(trans);
        for (int i = 0; i < selectedData.length; i++) {
            arrTime[i] = selectedData[i];
        }

        double[] waveletData = t.forward(arrTime);
//        double[] fftData = FastFourierTransform.processDataRealFFT(selectedData);
        if (waveletData.length > maxHz) {
            int coef = waveletData.length / maxHz;

            double[] ret = new double[maxHz];
            for (int i = 0; i < ret.length; i++) {
                for (int j = 0; j < coef; j++) {
                    ret[i] += waveletData[i * coef + j];
                }

            }
            return ret;
        }
        return waveletData;
//        return postProcess1Hz(waveletData, buildFrequencyAxis(waveletData));
    }

    public double getDesvioPadrao(int startSecond,
            int nbSeconds, double startPosition, double endPosition) {
        short[] selectedData = getSelectedSignalData(startSecond, nbSeconds,
                startPosition, endPosition);

        if (selectedData.length == 0) {
            return 0;
        }
        double[] doub = new double[selectedData.length];
        for (int i = 0; i < selectedData.length; i++) {
            doub[i] = selectedData[i];

        }

        return Estatistica.Estatistica.getDesvioPadrao(doub);

    }

    public double getMediaAritmetica(int startSecond,
            int nbSeconds, double startPosition, double endPosition) {
        short[] selectedData = getSelectedSignalData(startSecond, nbSeconds,
                startPosition, endPosition);

        if (selectedData.length == 0) {
            return 0;
        }
        double[] doub = new double[selectedData.length];
        for (int i = 0; i < selectedData.length; i++) {
            doub[i] = selectedData[i];

        }

        return Estatistica.Estatistica.getMediaAritmetica(doub);

    }

    public double getVariancia(int startSecond,
            int nbSeconds, double startPosition, double endPosition) {
        short[] selectedData = getSelectedSignalData(startSecond, nbSeconds,
                startPosition, endPosition);

        if (selectedData.length == 0) {
            return 0;
        }
        double[] doub = new double[selectedData.length];
        for (int i = 0; i < selectedData.length; i++) {
            doub[i] = selectedData[i];

        }

        return Estatistica.Estatistica.getVariancia(doub);

    }

    /**
     * @return Returns the edfFile.
     */
    public EDFFile getEdfFile() {
        return edfFile;
    }

    public double getSamplingRate() {
        samplingRate = (double) getNbSamples() / (double) edfFile.getRecordDuration();
        return samplingRate;
    }
}
